package cn.cloudtogo.server;

import cn.cloudtogo.common.Assert;
import cn.cloudtogo.common.GendbException;
import cn.cloudtogo.common.atomapi.ApiToken;
import cn.cloudtogo.common.atomapi.token.ApiResource;
import cn.cloudtogo.common.atomapi.token.ApiTokenAuthentication;
import cn.cloudtogo.common.atomapi.token.ApiTokenService;
import cn.cloudtogo.common.model.ApiTokenDao;
import cn.cloudtogo.common.model.ApiTokenModel;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;

/**
 * @author yaolianhua789@gmail.com
 **/
@Slf4j
public class ApiTokenInterceptor implements HandlerInterceptor {

    private final ApiTokenService apiTokenService;
    private final ApiTokenDao apiTokenDao;

    public ApiTokenInterceptor(ApiTokenService apiTokenService, ApiTokenDao apiTokenDao) {
        this.apiTokenService = apiTokenService;
        this.apiTokenDao = apiTokenDao;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HandlerMethod handlerMethod;
        if (handler instanceof HandlerMethod) {
            handlerMethod = (HandlerMethod) handler;
        } else {
            return true;
        }

        boolean hasApiToken = handlerMethod.hasMethodAnnotation(ApiToken.class);
        if (!hasApiToken) {
            return true;
        }

        boolean disabledApiToken = disabledApiToken(request);
        if (disabledApiToken) {
            return true;
        }

        String token = resolvedToken(request.getHeader("Authorization"));

        ApiTokenAuthentication authentication = build(token, request, handlerMethod);
        boolean authenticate = apiTokenService.authenticate(authentication);
        if (!authenticate) {
            log.info("Api token request uri. {}", request.getRequestURI());
            throw new GendbException("Unauthorized request");
        }

        return true;

    }

    /**
     * /api/:uuid/:table
     */
    private boolean disabledApiToken(HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        Assert.state(requestURI.startsWith("/api"), () -> "Unknown request uri: " + requestURI);
        String uuid = requestURI.split("/")[2];
        ApiTokenModel one = apiTokenDao.query(uuid, "Global");
        if (null == one) {
            log.warn("Can not found token model [" + uuid + "]");
            return false;
        }
        return one.isDisable();
    }

    private String resolvedToken(String token) {
        if (!StringUtils.hasText(token)) {
            throw new GendbException("Unauthorized request, Authorization is required");
        }
        if (token.startsWith("bearer") || token.startsWith("Bearer")) {
            return token;
        } else {
            return String.format("bearer %s", token);
        }
    }

    private ApiTokenAuthentication build(String token, HttpServletRequest request, HandlerMethod handlerMethod) {
        Method method = handlerMethod.getMethod();
        ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
        String httpMethod = apiOperation.httpMethod();
        String uri = request.getRequestURI();

        ApiResource apiResource = ApiResource.of(httpMethod, uri);
        cn.cloudtogo.common.atomapi.token.ApiToken apiToken = cn.cloudtogo.common.atomapi.token.ApiToken.of(token, "bearer");
        return ApiTokenAuthentication.of(apiToken, apiResource);
    }


}
